﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.IO;
using System.Resources;
using System.Collections;

namespace Kokomo.PeInspector.Reflection
{
	static class ReflectionExtensions
	{
		public static bool IsPropertyBackingField(this FieldInfo field)
		{
			if (field == null) throw new ArgumentNullException("field");

			bool isPropertyBackingField =
				field.Name.StartsWith("<")
				&& field.Name.EndsWith(">k__BackingField")
				;
			return isPropertyBackingField;
		}
		public static PropertyInfo Property(this MethodInfo method)
		{
			if (method == null) throw new ArgumentNullException("method");

			bool isAccessor =
				method.IsSpecialName
				&& (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
				;

			string propertyName = method.Name.Substring(4);
			BindingFlags flags = BindingFlags.DeclaredOnly
				| (method.IsStatic ? BindingFlags.Static : BindingFlags.Instance)
				| (BindingFlags.Public | BindingFlags.NonPublic)
				;

			PropertyInfo property = method.ReflectedType.GetProperty(propertyName, flags);
			return property;
		}
		public static bool IsPropertyAccessor(this MethodInfo method)
		{
			if (method == null) throw new ArgumentNullException("method");

			bool isAccessor =
				method.IsSpecialName
				&& (method.Name.StartsWith("get_") || method.Name.StartsWith("set_"))
				;
			return isAccessor;
		}
		public static bool HasBackingField(this PropertyInfo property)
		{
			return property.BackingField() != null;
		}
		public static FieldInfo BackingField(this PropertyInfo property)
		{
			if (property == null) throw new ArgumentNullException("property");

			string name = string.Format("<{0}>k__BackingField", property.Name);
			FieldInfo field = property.ReflectedType.GetField(name, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			return field;
		}
		public static bool HasBackingField(this EventInfo eventInfo)
		{
			return eventInfo.BackingField() != null;
		}
		public static FieldInfo BackingField(this EventInfo eventInfo)
		{
			if (eventInfo == null) throw new ArgumentNullException("eventInfo");

			BindingFlags flags =
				BindingFlags.Public
				| BindingFlags.NonPublic |
				((eventInfo.GetAddMethod(true).IsStatic) ? BindingFlags.Static : BindingFlags.Instance)
				;

			FieldInfo backingField = eventInfo.ReflectedType.GetField(eventInfo.Name, flags);

			return backingField;
		}
		public static bool IsEventAccessor(this MethodInfo method)
		{
			if (method == null) throw new ArgumentNullException("method");

			bool isAccessor =
				method.IsSpecialName
				&& (method.Name.StartsWith("add_") || method.Name.StartsWith("remove_"))
				;
			return isAccessor;
		}
		public static bool IsEventBackingField(FieldInfo field)
		{
			if (field == null) throw new ArgumentNullException("field");

			BindingFlags flags =
				BindingFlags.Public
				| BindingFlags.NonPublic |
				((field.IsStatic) ? BindingFlags.Static : BindingFlags.Instance)
				;

			EventInfo eventInfo = field.ReflectedType.GetEvent(field.Name, flags);

			return (eventInfo != null);
		}
		public static EventInfo Event(this MethodInfo method)
		{
			string eventName = null;
			if (method.Name.StartsWith("add_")) eventName = method.Name.Substring(4);
			else if (method.Name.StartsWith("remove")) eventName = method.Name.Substring(7);

			if (eventName == null) return null;

			BindingFlags flags =
				BindingFlags.Public
				| BindingFlags.NonPublic |
				((method.IsStatic) ? BindingFlags.Static : BindingFlags.Instance)
				;
			EventInfo @event = method.DeclaringType.GetEvent(eventName, flags);

			return @event;
		}
		public static bool IsAnonymousDelegateCacheField(this FieldInfo field)
		{
			if (field == null) throw new ArgumentNullException("field");

			bool isCache =
				(field.Name.StartsWith("CS$"))
				&& (field.FieldType.IsSubclassOf(typeof(Delegate)))
				;

			return isCache;
		}
		public static bool IsHiddenClassMember(this MemberInfo member)
		{
			if (member == null) throw new ArgumentNullException("member");

			FieldInfo field = member as FieldInfo;
			MethodInfo method = member as MethodInfo;

			bool isHidden =
				((field != null) && IsPropertyBackingField(field))
				|| ((method != null) && IsPropertyAccessor(method))
				|| ((field != null) && IsEventBackingField(field))
				|| ((method != null) && IsEventAccessor(method))
				|| (member.Name.StartsWith("CS$"))
				|| (member.Name.StartsWith("<"))
				;

			return isHidden;
		}
		public static string PrincipalName(this MemberInfo member)
		{
			if (member == null) throw new ArgumentNullException("member");

			string name = member.Name;
			if (!string.IsNullOrEmpty(name))
			{
				int tickIndex = name.IndexOf('`');
				if (tickIndex > 0)
					name = name.Substring(0, tickIndex);
			}
			else
			{
				name = string.Format("<unnamed{0}_{1:X}>",
					member.MemberType,
					member.MetadataToken
					);
			}

			return name;
		}
		public static string LocalName(this Type type)
		{
			string localName = type.PrincipalName();

			if (type.IsGenericType)
			{
				Type[] arguments = type.GetGenericArguments();
				if (arguments.Length > 0)
				{
					localName += "<";
					bool first = true;
					foreach (var argument in arguments)
					{
						if (first) first = false;
						else localName += ",";

						localName += argument.QualifiedFullName();
					}
					localName += ">";
				}
			}

			return localName;
		}
		public static string QualifiedFullName(this Type type)
		{
			if (type == null) throw new ArgumentNullException("type");

			if (type.IsGenericParameter) return type.Name;

			string qualifier =
				(type.IsNested)
				? type.ReflectedType.QualifiedFullName()
				: type.Namespace
				;

			string localName = type.LocalName();
			string qualified = string.Format("{0}.{1}", qualifier, localName);

			return qualified;
		}

		public static bool IsParamArray(this ParameterInfo parameter)
		{
			return (parameter.GetCustomAttributes(typeof(ParamArrayAttribute), true).Length > 0);
		}

		public static bool IsExtension(this MemberInfo member)
		{
			return (member.IsDefined(typeof(ExtensionAttribute), true));
		}

		public static bool IsRef(this ParameterInfo parameter)
		{
			return parameter.ParameterType.IsByRef && parameter.IsIn;
		}

		public static bool IsAnonymous(this Type type)
		{
			return type.Name.StartsWith("<");
		}

		public static bool IsPrivateInterfaceImplementation(this MethodInfo method)
		{
			return (method.IsPrivate && method.Name.Contains('.'));
		}
		public static bool IsPrivateInterfaceImplementation(this PropertyInfo property)
		{
			return (property.Name.Contains('.') && property.GetAccessors(true)[0].IsPrivate);
		}
		public static bool IsPrivateInterfaceImplementation(this EventInfo @event)
		{
			return (@event.Name.Contains('.') && @event.GetAddMethod(true).IsPrivate);
		}
		public static bool IsIndexer(this PropertyInfo property)
		{
			return ((property.Name == "Item") || (property.Name.EndsWith(".Item"))) && (property.GetIndexParameters().Length > 0);
		}
		public static Stream GetPackResource(this Assembly assembly, string resourcePath)
		{
			if (resourcePath == null) throw new ArgumentNullException("resourcePath");

			AssemblyName name = assembly.GetName();
			string packStreamName = name.Name + ".g.resources";
			Stream manifestResourceStream = assembly.GetManifestResourceStream(packStreamName);
			if (manifestResourceStream == null) return null;

			ResourceReader reader = new ResourceReader(manifestResourceStream);
			Stream packStream = null;
			foreach (DictionaryEntry entry in reader)
			{
				if (object.Equals(entry.Key, resourcePath))
				{
					packStream = entry.Value as Stream;
					break;
				}
			}

			return packStream;
		}
		public static TypeKind Kind(this Type type)
		{
			TypeKind kind;

			if (type.IsEnum) kind = TypeKind.Enumeration;
			else if (type.IsValueType) kind = TypeKind.Structure;
			else if (type.IsInterface) kind = TypeKind.Interface;
			else if (type.IsSubclassOf(typeof(Delegate))) kind = TypeKind.Delegate;
			else kind = TypeKind.Class;

			return kind;
		}

		public static MethodInfo GetImplementationOn(this MethodInfo interfaceMethod, Type type)
		{
			Type interfaceType = interfaceMethod.DeclaringType;
			if (!interfaceType.IsInterface) throw new ArgumentException(Messages.InvalidInterfaceMethodMessage, "interfaceMethod");
			if (type.IsInterface) throw new ArgumentException(Messages.CannotUseInterfaceTypeMessage, "type");

			Type[] implementedInterfaces = type.GetInterfaces();
			if (!implementedInterfaces.Contains(interfaceMethod.DeclaringType))
				return null;

			InterfaceMapping map = type.GetInterfaceMap(interfaceType);
			if (map.InterfaceMethods == null) return null;

			int methodSlot = Array.IndexOf(map.InterfaceMethods, interfaceMethod);
			if (methodSlot < 0) return null;

			MethodInfo method = map.TargetMethods[methodSlot];
			return method;
		}
		public static bool IsImplementationOf(this MethodBase method, MethodInfo interfaceMethod)
		{
			MethodInfo methodInfo = method as MethodInfo;
			if (methodInfo == null) return false;

			if (interfaceMethod == null) throw new ArgumentNullException("interfaceMethod");
			if (
				(interfaceMethod.DeclaringType is Nullable)
				|| (!interfaceMethod.DeclaringType.IsInterface)
				|| (method.DeclaringType == null)
				|| (method.DeclaringType.IsInterface)
				)
				return false;

			MethodInfo implementation = interfaceMethod.GetImplementationOn(method.DeclaringType);
			MethodInfo baseMethod = methodInfo.GetBaseDefinition();
			bool isImplementation =
				(method == implementation)
				|| (baseMethod == implementation)
				;

			return isImplementation;
		}

		public static bool References(this Assembly assembly, AssemblyName referencedAssembly)
		{
			if (referencedAssembly == null) throw new ArgumentNullException("referencedAssembly");

			AssemblyName[] references = assembly.GetReferencedAssemblies();
			return references.Contains(referencedAssembly);
		}

		private static bool DerivesFrom(this Type type, string baseTypeFullName)
		{
			while (type != null)
			{
				if (type.FullName == baseTypeFullName)
					return true;
				type = type.BaseType;
			}
			return false;
		}
		public static bool IsAttribute(this Type type)
		{
			return type.DerivesFrom("System.Attribute");
		}
		public static bool IsException(this Type type)
		{
			return type.DerivesFrom("System.Exception");
		}
	}
}
